/**
 * Copyright (c) 2009, 3BitSoftware
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the 3BitSoftware nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY 3BitSoftware ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.threebitsoftware.tbb.module.webservice;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import org.threebitsoftware.tbb.dao.Address;
import org.threebitsoftware.tbb.dao.Contact;
import org.threebitsoftware.tbb.dao.EmailAddress;
import org.threebitsoftware.tbb.module.webservice.WebServiceInterface;

import javax.swing.JPanel;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Vector;
import java.util.regex.*;

import com.google.gdata.client.Service;
import com.google.gdata.client.contacts.ContactsService;
import com.google.gdata.data.contacts.ContactEntry;
import com.google.gdata.data.contacts.ContactFeed;
import com.google.gdata.data.contacts.ContactGroupEntry;
import com.google.gdata.data.contacts.GroupMembershipInfo;
import com.google.gdata.data.extensions.Deleted;
import com.google.gdata.data.extensions.ExtendedProperty;
import com.google.gdata.data.extensions.FamilyName;
import com.google.gdata.data.extensions.FormattedAddress;
import com.google.gdata.data.extensions.FullName;
import com.google.gdata.data.extensions.GivenName;
import com.google.gdata.data.extensions.Name;
import com.google.gdata.data.extensions.NamePrefix;
import com.google.gdata.data.extensions.NameSuffix;
import com.google.gdata.data.extensions.OrgName;
import com.google.gdata.data.extensions.OrgTitle;
import com.google.gdata.data.extensions.Organization;
import com.google.gdata.data.extensions.PhoneNumber;
import com.google.gdata.data.extensions.StructuredPostalAddress;
import com.google.gdata.data.extensions.Email;
import com.google.gdata.util.ContentType;
import com.google.gdata.data.Extension;
import com.google.gdata.data.ExtensionDescription;
import com.google.gdata.data.ExtensionProfile;
import com.google.gdata.data.Link;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.util.NoLongerAvailableException;
import com.google.gdata.util.ServiceException;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.common.xml.XmlNamespace;

/**
 * Gcontacts connects to Google Contacts to either retrieve or upload contacts
 */
public class Gcontacts implements WebServiceInterface {
	
	private URL                 baseUrl;
	private ContactsService     gContactsSvc;
	private static final String urlStem      = "http://www.google.com/m8/feeds/";
	private static final String projection   = "full";
	private static final short  ZERO         = 0;
	private static final String baseRel		 = "http://schemas.google.com/g/2005#";
	
	private static Logger gLogger = Logger.getLogger(Gcontacts.class);
	
	/**
	 * @param username
	 * @param password
	 * @throws AuthenticationException
	 * @throws MalformedURLException
	 */
	public Gcontacts(String username, String password) 
				throws AuthenticationException, MalformedURLException {
		initializeParams(username, password, urlStem);
	}
	
	/**
	 * default constuctor
	 */
	public Gcontacts() {
		
	}

	/**
	 * @param username
	 * @param password
	 * @param url
	 * @return void
	 * @throws AuthenticationException
	 * @throws MalformedURLException
	 */
	public void initializeParams(String username, String password, String url) 
				throws AuthenticationException, MalformedURLException{

		if(url == null) {
			url = urlStem;
		}
		
		gContactsSvc = new ContactsService("3BitSoftware's Traveling Black Book");
		try {
			
			/**
			 * get service from Google with valid username and password
			 */
			gContactsSvc. setUserCredentials(username, password);
			baseUrl = new URL(url + "contacts/" + username + "/" + projection);
		
		} catch (AuthenticationException ex) {
			
			gLogger.error("Credential provided is invalid", ex);
			throw new AuthenticationException("Credential provided is invalid");
		
		} catch (MalformedURLException ex) {
		
			gLogger.error("Invalid Url: username",ex);
			throw new MalformedURLException("Invalid Url: username");	
		}
	}

	/**
	 * @param contacts
	 * @return void
	 * @throws Exception
	 */
	public void publishContacts(Contact[] contacts) throws Exception {
		if(contacts != null) {
			/**
			 * push the whole array to Google
			 */
			for (Contact aTbbContact : contacts) {
				ContactEntry contactEntry = new ContactEntry();

				/**
				 * Convert TBB company, title to Google Organization and title 
				 * format
				 */
				Organization org = new Organization();
				org.setOrgName(new OrgName(aTbbContact.getCompany()));
				org.setOrgTitle(new OrgTitle(aTbbContact.getTitle()));
				org.setRel(baseRel + "work");
				contactEntry.addOrganization(org);
			
				/**
				 * convert TBB Name to Google Name format
				 */
				Name name = new Name();
				name.setFullName(new FullName(aTbbContact.getFirstName() + " " + 
						aTbbContact.getLastName() + " " + 
						aTbbContact.getMiddleName(),null));
//				name.setNamePrefix(new NamePrefix(aTbbContact.getPrefix()));
//				name.setNameSuffix(new NameSuffix(aTbbContact.getSuffix()));
//				name.setGivenName(new GivenName(aTbbContact.getFirstName(),null));
//				name.setFamilyName(new FamilyName(aTbbContact.getLastName(),null));
			
				contactEntry.setName(name);
		
				/**
				 * convert TBB Address to Google Address format
				 */
				if(aTbbContact.getAddresses() != null) {
					for(Address addr : aTbbContact.getAddresses()) {
						/**
						 * assume address format is:
						 *  ADDRESS: 500 Boston St USA Boston, MA 02118
						 */
						StructuredPostalAddress gAddr = new StructuredPostalAddress();
				
						FormattedAddress fAddr = new FormattedAddress();				
						fAddr.setValue(addr.getStreet1() + " " + addr.getCountry() +
								" "	+ addr.getCity() + " " + addr.getProvince() + 
								" " + addr.getZipCode());
						gAddr.setFormattedAddress(fAddr);
						gAddr.setRel(baseRel + addr.getDescription());
						contactEntry.addStructuredPostalAddress(gAddr);
					}
				}
			
				/**
				 * Convert TBB Email to Google Email format
				 */
				if(aTbbContact.getEmails() != null) {
					for(EmailAddress email : aTbbContact.getEmails()) {
						Email anEmail = new Email();
						anEmail.setAddress(email.getEmailAddress());
						anEmail.setRel(baseRel + email.getDescription());
						contactEntry.addEmailAddress(anEmail);
					}
				}

				/**
				 * Convert TBB PhoneNumber to Google PhoneNumber format
				 */
				if(aTbbContact.getPhoneNumbers() != null) {
					for(org.threebitsoftware.tbb.dao.PhoneNumber t2bPhone : 
						aTbbContact.getPhoneNumbers()) {
						PhoneNumber gPhone = new PhoneNumber();
						gPhone.setPhoneNumber(t2bPhone.getPhoneNumber());
						gPhone.setRel(baseRel + t2bPhone.getDescription());
						contactEntry.addPhoneNumber(gPhone);
					}
				}

		
				
				/**
				 * Convert TBB Contact's Group to Google Contacts GroupMembership 
				 */
//				if(aTbbContact.getTags()!= null) {
//					for(String tag : aTbbContact.getTags()) {
//						GroupMembershipInfo grp = new GroupMembershipInfo(false, tag);
//						contactEntry.addGroupMembershipInfo(grp);
//					}
//				}

				
				try {
								
					/**
					 * Add the convert Contact to Google Contacts
					 */					

					ContactEntry insertedContact = gContactsSvc.insert(baseUrl, contactEntry);
					if(aTbbContact.getPhoto().length > 0) {
						sendPhoto(insertedContact,aTbbContact.getPhoto());
					}
				} catch(IOException ex) {
					gLogger.error("Fail to add new contact to Google",ex);
					throw new Exception("Fail to add new contact to Google");
				} catch(ServiceException ex) {
					gLogger.error("Fail to add new contact to Google",ex);
					System.out.println(ex);
					throw new Exception("Fail to add new contact to Google");
				}
			}
		}
	}

	/**
	 * This method returns a list of translated Contacts (TBB's Contact format)
	 *  and pass them back to the caller function
	 * @return Contact[]
	 */
	public Contact[] getContacts() {

		Contact[] listOfContact = null;
		
		try {
			ContactFeed googleContactFeed      = null;
			googleContactFeed = gContactsSvc.getFeed(baseUrl, ContactFeed.class);
			
			int contactCount  = ZERO;
			listOfContact     = new Contact[googleContactFeed.getEntries().size()];
			
			for(ContactEntry aGcontact : googleContactFeed.getEntries()) {
				
				String company = "";
				String first   = "";
				String last    = "";
				String initial = "";
				String prefix  = "";
				String suffix  = "";
				String title   = "";
				
				Vector<Address> addresses          = null;
				Vector<EmailAddress> emails        = null;
				Vector<org.threebitsoftware.tbb.dao.PhoneNumber> phones = null;
				Vector<String> tags                = null;
				
				byte[] photo                       = null;

				/**
				 * organization
				 */
				if(aGcontact.hasOrganizations()) {
					Organization org = aGcontact.getOrganizations().get(ZERO);

					company = org.getOrgName().getValue();	
					title   = org.getOrgTitle().getValue();
				}
								
				Name name = aGcontact.getName();
				if(name != null && name.hasFullName()) {

					String contactFullName = name.getFullName().getValue();
					String [] parsedName = contactFullName.split(" ");
					
					/**
					 * case first; case first last; case first initial last
					 */
					switch (parsedName.length) {
						case 1:
							first = parsedName[0];
							break;
						case 2:
							first = parsedName[0];
							last  = parsedName[1];
							break;							
						case 3:
							first   = parsedName[0];
							initial = parsedName[1];
							last    = parsedName[2];
							break;
						default:
							break;
					}
										
					if(name.hasNamePrefix())
						prefix = name.getNamePrefix().getValue();
					
					if(name.hasNameSuffix())
						suffix = name.getNameSuffix().getValue();
				}
				
				/**
				 * convert address(s)from Google format to TBB format
				 */
				if(aGcontact.hasStructuredPostalAddresses()) {
					addresses = convertToTbbAddress(
							aGcontact.getStructuredPostalAddresses());
				}

				/**
				 * convert Emails from Google format to TBB format
				 */
				if(aGcontact.hasEmailAddresses()){
					emails = convertToTbbEmail(aGcontact.getEmailAddresses());
				}
				
				/**
				 * convert PhoneNumbers from Google format to TBB format
				 */
				if(aGcontact.hasPhoneNumbers()){
					phones = convertToTbbPhone(aGcontact.getPhoneNumbers());
				}
				
				/**
				 * convert GroupMemebership from Google format to TBB format
				 */
				if(aGcontact.hasGroupMembershipInfos()) {
					tags = convertToTbbTags(aGcontact.getGroupMembershipInfos());
				}
				
				// create an instance of TBB's Contact
				Contact aTbbContact = new Contact(first, last, initial, suffix, 
						prefix, company, title, true);
				
				/**
				 * set the phones, addresses and emails for the contact
				 */
				aTbbContact.setPhoneNumbers(phones);
				aTbbContact.setAddresses(addresses);
				aTbbContact.setEmails(emails);
				aTbbContact.setTags(tags);
				
				/**
				 * get a byte[] of photo representation and add it to the TBB 
				 * contact
				 */
				photo = getPhoto(aGcontact);
				aTbbContact.setPhoto(photo);
				
				/**
				 * add newly created TBB contact format to the cumulative list 
				 * of translated contact
				 */
				listOfContact[contactCount] = aTbbContact;
				contactCount++;
			}
		} catch(NoLongerAvailableException ex) {
			gLogger.error(ex);
		} catch(IOException ex) {
			gLogger.error(ex);
		} catch(ServiceException ex) {
			gLogger.error(ex);	
		}
		
		return listOfContact;
	}

	/**
	 * translate the Google Group Membership List to a list of TBB tags 
	 * @param googleContactGroup
	 * @return
	 */
	private Vector<String> convertToTbbTags(List<GroupMembershipInfo> 
			googleContactGroup) {
		Vector<String> listOfTags = new Vector<String>();
		
		for(GroupMembershipInfo membership: googleContactGroup) {
			listOfTags.add(membership.toString());
		}
		return listOfTags;
	}
	
	/**
	 * translate the Google Phone Numbers to TBB Phone Numbers
	 * @param googlePhone
	 * @return Vector<org.threebitsoftware.tbb.dao.PhoneNumber>
	 */
	private Vector<org.threebitsoftware.tbb.dao.PhoneNumber> 
		convertToTbbPhone(List<PhoneNumber> googlePhone) {
		Vector<org.threebitsoftware.tbb.dao.PhoneNumber> listOfPhone = 
				new Vector<org.threebitsoftware.tbb.dao.PhoneNumber>();
		
		for(PhoneNumber phone : googlePhone) {
			listOfPhone.add(new org.threebitsoftware.tbb.dao.PhoneNumber(
					phone.getPhoneNumber(),formatRel(phone.getRel())));
		}
		return listOfPhone;
	}

	/**
	 * translate the Google Emails to TBB Emails
	 * @param googleEmail
	 * @return Vector<EmailAddress>
	 */
	private Vector<EmailAddress> convertToTbbEmail(List<Email> googleEmail) {
		Vector<EmailAddress> listOfEmail = new Vector<EmailAddress>();
		
		for(Email email : googleEmail) {
			listOfEmail.add(new EmailAddress(email.getAddress().toString(),
					formatRel(email.getRel())));
		}
		return listOfEmail;	
	}
	
	/**
	 * translate the Google Address to Tbb Address
	 * @param googleAddress
	 * @return Vector<Address>
	 */
	private Vector<Address> convertToTbbAddress(List<StructuredPostalAddress> 
		googleAddress) {
		Vector<Address> listOfAddress = new Vector<Address>();
		
		for(StructuredPostalAddress address : googleAddress) {
			//500 Boston St USA Boston, MA 02116
			//final String pattern = "(\\d+)\\s+(\\w+)\\s+(\\w+)\\s+(\\w+)" + 
			//"\\s+(\\w+,|\\w+)\\s+(\\w+)\\s+(\\d+|\\d+-\\d+)";
			
			final String pattern = "(\\d+)\\s+" + // 500    \n
            "(\\w+)\\s+" + // Boston  \n
            "(\\w+)\\s+" + // st      \n 
            "(\\w+|\\w+\\s+\\w+|\\w+\\s+\\w+\\s+\\w+|\\w+\\s+\\w+\\s+\\w+\\s+\\w+)\\s+" + // USA or Czech Republic or United Arab Emerate or United States of Amercia \n
            "(\\w+,|\\w+|\\w+\\s+\\w+|\\w+\\s+\\w+,)\\s+" + //Boston, or Boston or San Diego or San Deigo, \n
            "(\\w+)\\s+" + // MA \n
            "(\\d+|\\d+-\\d+)";	// 02116-9999
						
			Pattern p = Pattern.compile(pattern);
			Matcher m = p.matcher(address.getFormattedAddress().getValue());
			
//			group 0	=>500        
//			Boston 
//			st  
//			USA boston, MA 02116-9999
//
//			group 1	=>500
//			group 2	=>Boston
//			group 3	=>st
//			group 4	=>USA
//			group 5	=>boston,
//			group 6	=>MA
//			group 7	=>02116-9999
//			
//			public Address(String strStreet1, String strStreet2, String strStreet3,
//					String strCountry, String strZipCode, String strProvince,
//					String strCity, String strDescription) {
			
			if(m.matches()) {
				listOfAddress.add(new Address(m.group(1) + " " + m.group(2) + 
						" "	+ m.group(3),"","", m.group(4),m.group(7),m.group(6),
						m.group(5),	formatRel(address.getRel())));
			}
		}
		return listOfAddress;
	}
	
	/**
	 * get the photo of the contact from Google and return it as a byte array
	 * @param entry
	 * @return byte[]
	 * @throws ServiceException
	 * @throws IOException
	 */
	public byte[] getPhoto(ContactEntry entry) throws ServiceException, 
		IOException {
		
		/**
		 * Get the link to the photos
		 */
		Link photoLink = entry.getLink(
				"http://schemas.google.com/contacts/2008/rel#photo", "image/*");
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		
		if (photoLink.getEtag() != null) {
			Service.GDataRequest request = 
				gContactsSvc.createLinkQueryRequest(photoLink);
			request.execute();
			
			/**
			 * read from the photo from the stream
			 */
			InputStream in = request.getResponseStream();
	        byte[] buffer = new byte[4096];
	       
	        int read = 0;
	        while((read = in.read(buffer)) != -1) {
	        	out.write(buffer, 0, read);
	        }
	        
	        in.close();
	        request.end();	        
	      }
	      return out.toByteArray();
	}
	
	/**
	 * take the photo from the TBB contact and send it to the corresponding 
	 * translated Google Contact
	 * @param bealoon
	 * @param entry
	 * @param photo
	 * @return void
	 */
	public void sendPhoto(ContactEntry entry, byte[] photo) {
	    Link photoLink = entry.getContactPhotoLink();
	    
	    try {
	    	URL photoUrl = new URL(photoLink.getHref());
	    	Service.GDataRequest request = gContactsSvc.createRequest(
	    			Service.GDataRequest.RequestType.UPDATE, photoUrl, 
	    				new ContentType("image/gif"));
	    	request.setEtag(photoLink.getEtag());
	    	OutputStream requestStream = request.getRequestStream();
	    	requestStream.write(photo);
	    	request.execute();
	    } catch(MalformedURLException mfEx) {
			gLogger.log(Level.ERROR, mfEx.getCause());
	    } catch(ServiceException svcEx) {
	    	gLogger.log(Level.ERROR, svcEx.getCause());
	    } catch(IOException ioe) {
	    	gLogger.log(Level.ERROR, ioe.getCause());
	    }
	}

	/**
	 * 
	 * @return
	 */
	private String formatRel(String rel) {
		return rel.substring(rel.indexOf("#") + 1).split(" ")[0];
	}
	
	@Override
	public JPanel getGUI() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getMergeClassName() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean iHaveMyOwnGUI() {
		// TODO Auto-generated method stub
		return false;
	}
}
